// pages/ai/ai.js
import {
  server
} from "../../utils/config.js";
// 防抖函数
import debounce from "../../utils/debounce.js";
// 获取小程序实例
const _app = getApp();
// 配置
const {
  serverApi
} = require("../../utils/config.js");
// jwt工具
const {
  hasJwt,
  getJwt
} = require("../../utils/jwt.js");
// 同声传译插件
let plugin = requirePlugin("WechatSI");
// 语音识别管理器
let manager = plugin.getRecordRecognitionManager();
// 语音识别状态
let recordRecognitionStatus = false;
// 请求任务
let requestTask;
// 超时计时器
let timeoutTimer;
// 触摸状态，true手指按下，false手指抬起
let touchStatus = false;
// 开始说话时间，毫秒
let startTalkTime = 0;
// 当前会话ID
let sessionID = "0";
// 会话最后一条的ID，用于分页，每页大小10，返回不足10则无新数据
let sessionLastId = 922337203685477;
// 对话最后一条ID，用于分页，每页大小10，返回不足10则无新数据
let dialogLastId = 0;

Page({

  /**
   * 页面的初始数据
   */
  data: {
    server: server,
    jxtToast: undefined, // toast组件
    systemStatusHeight: 30, // 系统状态栏高度
    menuWidth: 64, // 菜单宽度
    navigationBarHeight: 48, //导航栏高度
    scrollHeight: 1000, //滚动高度
    isShowSession: false, // 控制会话列表展示
    scrollSessionHeight: 'auto', // 会话列表高度
    keyboardHeight: 0, // 键盘高度
    currentSessionId: 0, // 当前会话
    sessionList: [], // 会话列表
    hasNewSessionPage: true, //是否存在新的会话页
    isLoadingSession: false, // 是否等待会话
    dialogList: [], //对话列表
    hasNewDialogPage: false, // 是否存在新的对话页
    info: {
      welcome: "Hi~我是西工商Ai辅助教学助手",
      tips: [
        "我能提供作业答疑、知识点解析，",
        "还能辅助制定学习计划～"
      ],
      recommend: [
        "微信小程序如何发起网络请求？",
        "scroll-view组件支持哪些属性？",
        "微信小程序如何获取屏幕的宽高？",
        "如何设计一款友好、高效、一致用户体验的页面？"
      ], // 推荐问答列表
      placeholder: "有问题，随便问~",
    }, // 信息
    isShowMoreMenu: false, // 控制更多菜单显示
    addMoreRotate: 0, // 更多按钮旋转角度
    imageList: [], //用户上传的图片列表
    inputContent: "", // 用户输入的内容
    isDisableSend: true, // 是否禁用发送按钮
    isLoadingAi: false, // 是否正在等待AI
    scrollIntoId: "", // 滚动区域底部标志id，需要滚动到底部时设置为bottom
    isAutoScrollBottom: true, // 是否自动滚动到底部
    isShowOneTouchScrollBottom: false, // 是否显示一键滚动到底部按钮
    isLogin: false, //是否登录
    inputMode: "keyboard", // 输入模式 keyboard | voice
    isTalk: false, //是否开始说话
  },

  /**
   * 对话页面被点击
   */
  aiChatClick() {
    // 复位
    this.setData({
      isShowSession: false,
      isShowMoreMenu: false,
      addMoreRotate: 0,
    })
  },

  /**
   * 切换输入模式
   */
  changeInputModeClick() {
    // 切换语音输入时检查权限
    if (this.data.inputMode === "keyboard") {
      // 申请麦克风权限
      wx.getSetting({
        success(res) {
          if (!res.authSetting['scope.record']) {
            wx.authorize({
              scope: 'scope.record',
              success() {}
            })
          }
        }
      });
    }
    this.setData({
      inputMode: this.data.inputMode === "keyboard" ? "voice" : "keyboard"
    });
  },

  /**
   * 一键滚动到底部
   */
  oneTouchClick() {
    let that = this;
    that.setData({
      scrollIntoId: "bottom"
    });
    // 下一个时间片执行
    wx.nextTick(() => {
      that.setData({
        scrollIntoId: ""
      })
    });
  },

  /**
   * 滚动到顶部时触发
   */
  bindScrollToUpperHandle(e) {
    if (this.data.hasNewDialogPage) {
      this.getDialogs();
    }
  },

  /**
   * 距离底部适当距离时触发
   */
  bindScrollToLowerHandle(e) {
    // 允许自动滚动并隐藏一键滚动按钮
    this.setData({
      isAutoScrollBottom: true,
      isShowOneTouchScrollBottom: false
    });
  },

  /**
   * 对话区滚动时触发
   */
  bindScrollHandle(e) {
    let that = this;
    let scrollTop = e.detail.scrollTop;
    let heightDiffer = e.detail.scrollHeight - this.data.scrollHeight;
    let criticalValue = heightDiffer - 50;

    if (scrollTop < criticalValue) {
      // 用户滚动，不再自动滚动到底部
      that.setData({
        isAutoScrollBottom: false,
        isShowOneTouchScrollBottom: true
      });
    }
  },

  /**
   * 复制文本
   */
  copyContentClick(res) {
    wx.setClipboardData({
      data: res.currentTarget.dataset.content,
    })
  },

  /**
   * 点击推荐问答
   */
  recommendClick(e) {
    this.addDialog('user', e.currentTarget.dataset.problem);
    this.scrollableBottom();
  },

  /**
   * 发送消息
   */
  sendClick() {
    let that = this;
    if (that.data.isDisableSend) {
      return;
    }
    let len = that.data.imageList.length;
    if (len > 0 && that.data.imageList[len - 1].content === "") {
      that.data.jxtToast.showToast("等待图片识别", "prompt");
      return;
    }
    that.addDialog('user', that.data.inputContent);
    that.setData({
      inputContent: '',
      isDisableSend: true
    })
    that.scrollableBottom();
  },

  /**
   * 添加对话
   */
  addDialog(role, content, finish_reason = 'stop', htmlContent = null, id = 0) {
    if (id = 0) {
      id = this.data.dialogList.length;
    }
    const dialog = {
      id,
      role,
      content,
      htmlContent,
      finish_reason,
      images: []
    }
    if (role === "user") {
      let imgContent = "";
      const images = [];
      this.data.imageList.forEach((i) => {
        if (i.content !== "") {
          imgContent += i.content;
          images.push({
            id: i.id,
            path: i.path
          });
        }
      });
      dialog.images = images;
      this.data.dialogList.push(dialog);
      this.setData({
        imageList: [],
        isLoadingAi: true,
        dialogList: this.data.dialogList
      });
      this.sendMessage(content + imgContent);
    } else {
      this.data.dialogList.push(dialog);
      this.setData({
        dialogList: this.data.dialogList
      });
    }
  },

  /**
   * 发送消息
   * @param {string} message 
   */
  sendMessage(message) {
    let that = this;

    that.sendMessageToWorker({
      content: "start"
    });

    let sid = hasJwt() ? sessionID : "0";

    that.addDialog("assistant", null, "wait");

    // 超时计算
    that.timeoutError();

    requestTask = wx.request({
      url: serverApi + "/ai/chat/" + sid,
      enableChunked: true,
      header: {
        "Accept": "text/event-stream",
        "Content-Type": "application/json",
        "Connection": "keep-alive",
      },
      data: {
        message
      },
      fail(res) {
        that.setData({
          isLoadingAi: false
        })
        if (res.errMsg !== "request:fail abort") {
          that.happenError();
        }
      }
    });

    requestTask.onChunkReceived(response => {
      clearTimeout(timeoutTimer);
      if (!that.data.isLoadingAi) {
        return;
      }
      if (response.data && Object.prototype.toString.call(response.data).slice(8, -1) === "ArrayBuffer") {
        that.sendMessageToWorker({
          content: response.data
        });
      }
    });
  },

  /**
   * 停止Ai回复
   */
  stopAiClick() {
    let index = this.data.dialogList.length - 1;
    this.data.dialogList[index].finish_reason = "stop";
    this.setData({
      isLoadingAi: false,
      dialogList: this.data.dialogList
    });
    this.happenError("", "stop");
    try {
      requestTask.abort();
    } catch (_) {}
  },

  /**
   * 发生错误
   */
  happenError(message = "服务繁忙，请稍后重试！", type = "error") {
    let that = this;
    clearTimeout(timeoutTimer);
    let index = that.data.dialogList.length - 1;
    that.data.dialogList[index].finish_reason = type;
    if (type === "error" && that.data.dialogList[index].htmlContent === null) {
      that.data.dialogList[index].htmlContent = message;
    }
    that.setData({
      isLoadingAi: false,
      dialogList: that.data.dialogList
    });
    // 通知worker
    that.sendMessageToWorker({
      content: "end"
    });
  },

  /**
   * 超时无回复,1分钟无回复
   */
  timeoutError() {
    let that = this;
    timeoutTimer = setTimeout(() => {
      that.happenError("服务繁忙，请稍后重试！");
    }, 60000);
  },

  /**
   * 滚动到页面底部
   */
  scrollableBottom() {
    let that = this;
    if (!that.data.isAutoScrollBottom) {
      return;
    }
    that.setData({
      scrollIntoId: "bottom"
    });
    // 下一个时间片执行
    wx.nextTick(() => {
      that.setData({
        scrollIntoId: ""
      })
    });
  },

  /**
   * 开始按下说话，手指触摸
   */
  holdTalkTouchStart() {
    let that = this;
    if (recordRecognitionStatus) {
      that.data.jxtToast.showToast("等待已有语音识别结束", "prompt");
      return;
    }
    touchStatus = true;
    // 延迟300毫秒
    setTimeout(() => {
      if (touchStatus) {
        startTalkTime = new Date().getTime();
        manager.start({
          duration: 30000,
          lang: "zh_CN"
        });
      }
    }, 300);
  },

  /**
   * 结束说话，手指抬起
   */
  holdTalkTouchEnd() {
    touchStatus = false;
    let endTalkTime = new Date().getTime();
    console.log(endTalkTime - startTalkTime);
    startTalkTime = 0;
    // 停止录音
    manager.stop();
    this.setData({
      isTalk: false
    });
    console.log("抬起")
  },

  /**
   * 初始化语音识别管理器
   */
  initRecordRecognition() {

    let that = this;

    manager.onStop = function (res) {
      recordRecognitionStatus = false;
      console.log("record file path", res.tempFilePath)
      console.log("result", res.result)
      that.setData({
        inputMode: "keyboard",
        inputContent: res.result,
        isDisableSend: false,
      });
    };

    manager.onError = (res) => {
      recordRecognitionStatus = false;
      console.log("错误", res.msg)
    }

    manager.onStart = (res) => {
      recordRecognitionStatus = true;
      that.setData({
        isTalk: true
      });
    }
  },

  /**
   * 用户输入文本
   */
  inputContentHandle(e) {
    this.data.inputContent = e.detail.value
    if (this.data.inputContent.trim().length > 0) {
      this.setData({
        isDisableSend: false,
        inputMode: "keyboard"
      })
    } else {
      this.setData({
        isDisableSend: true
      })
    }
  },

  /**
   * 清空对话
   */
  clearChatClick() {
    let that = this;
    if (that.data.dialogList.length === 0) {
      that.data.jxtToast.showToast("空的", "prompt");
      return;
    }
    wx.showModal({
      title: '清空对话？',
      complete: (res) => {
        if (res.confirm) {
          this.getSid();
          that.setData({
            dialogList: []
          });
          that.data.jxtToast.showToast("已清空", "success");
        }
      }
    })
  },

  /**
   * 上传图片
   */
  upPicturesClick(e) {
    let that = this;
    // 限制6张图片
    if (that.data.imageList.length >= 6) {
      that.data.jxtToast.showToast("图片达到限制", "warning");
      return;
    }
    let id = that.data.imageList.length;
    if (id > 0 && that.data.imageList[id - 1].content === "") {
      that.data.jxtToast.showToast("等待图片识别", "prompt");
      return;
    }
    let source = e.currentTarget.dataset.source;
    wx.chooseMedia({
      count: 1,
      mediaType: ['image'],
      sourceType: [source],
      camera: 'back',
      success(res) {
        let size = Math.ceil(res.tempFiles[0].size / 1024 / 1024 * 100) / 100;
        if (size > 4) {
          that.data.jxtToast.showToast("图片大小不超过4MB", "warning");
          return;
        }
        that.ocr(res.tempFiles[0].tempFilePath);
      }
    })
  },

  /**
   * 识别图片
   */
  ocr(filePath) {
    let that = this;
    let id = that.data.imageList.length + 1;
    that.data.imageList.push({
      id,
      path: filePath,
      content: ""
    });
    that.setData({
      imageList: that.data.imageList
    })
    wx.uploadFile({
      filePath: filePath,
      name: 'resFile',
      url: serverApi + "/ocr",
      success(res) {
        let resData = JSON.parse(res.data);
        if (resData.code === 200) {
          that.data.imageList[id - 1].content = "[用户发来第" + id + "张图片内容为<" + resData.data.content + ">]";
          that.setData({
            imageList: that.data.imageList
          })
        } else {
          that.data.imageList.pop();
          that.setData({
            imageList: that.data.imageList
          })
          that.data.jxtToast.showToast(resDate.message, "warning");
        }
      },
      fail(err) {
        that.data.imageList.pop();
        that.setData({
          imageList: that.data.imageList
        })
        that.data.jxtToast.showToast("解析图片失败", "error");
      }
    })
  },

  /**
   * 删除图片
   */
  deleteImageClick(e) {
    this.setData({
      imageList: this.data.imageList.filter(item => item.id !== e.currentTarget.dataset.id)
    })
  },

  /**
   * 预览图片
   */
  previewImageClick(e) {
    const dataset = e.currentTarget.dataset
    let imgs = dataset.images.map((item) => {
      return item.path;
    });
    wx.previewImage({
      current: dataset.current,
      urls: imgs,
    })
  },

  /**
   * 控制更多菜单显隐藏
   */
  controlMoreMenuClick() {
    let is = this.data.isShowMoreMenu;
    let rotate = this.data.addMoreRotate;
    if (is) {
      rotate = 0
    } else {
      rotate = 45
    }
    this.setData({
      isShowMoreMenu: !is,
      addMoreRotate: rotate
    });
  },

  /**
   * 键盘高度变化
   */
  bindkeyboardheightchangeHandle(e) {
    this.setData({
      keyboardHeight: e.detail.height
    })
  },

  /**
   * 打开会话列表
   */
  openSessionListClick() {
    this.setData({
      isShowSession: true
    });
  },

  /**
   * 监听worker消息
   */
  workerMessageHandle(res) {
    if (res.type === "towxml") {
      this.parseAiNormalDialog(res.data);
    } else if (res.type === "article") {
      this.parseAssistantDialog(res.data);
    }
  },

  /**
   * 向worker发送通知
   * @param {Object} data 键值对
   * @param {string} type 类型
   */
  sendMessageToWorker(data, type = "towxml") {
    _app.sendMessageToWorker(type, data);
  },

  /**
   * 解析正常Ai对话内容
   * @param {object} data 
   */
  parseAiNormalDialog(data) {
    const that = this;
    let index = that.data.dialogList.length - 1;
    if (that.data.dialogList[index].finish_reason === "stop" ||
      that.data.dialogList[index].finish_reason === "error") {
      return;
    }
    // 停止
    if (!that.data.isLoadingAi) {
      that.data.dialogList[index].finish_reason = "stop";
    } else {
      that.data.dialogList[index].htmlContent = data.content;
      if (data.reason === "stop" || data.reason === "error") {
        that.data.dialogList[index].finish_reason = data.reason;
        that.data.dialogList[index].content = data.content.txt;
        that.setData({
          isLoadingAi: false
        })
      }
    }
    that.setData({
      dialogList: that.data.dialogList
    });
    that.scrollableBottom();
  },

  /**
   * 获取对话列表
   */
  getDialogs() {
    const that = this;
    wx.fetch({
        url: "/ai/dialog",
        data: {
          conversationId: sessionID
        }
      })
      .then(res => {
        const resData = res.data;
        let hndp = false;
        if (resData.dialogs.length > 0) {
          dialogLastId = resData.dialogs[resData.dialogs.length - 1].id;
          const dList = resData.dialogs;
          that.setData({
            dialogList: [...dList, ...that.data.dialogList],
            hasNewDialogPage: hndp,
          });
          // ai内容交给worker处理
          dList.forEach(d => {
            if (d.role === "assistant") {
              that.sendMessageToWorker({
                content: d.content,
                did: d.id
              }, "article");
            }
          });
          that.scrollableBottom();
        } else {
          hndp = false;
          that.setData({
            hasNewDialogPage: hndp,
          });
        }
      })
  },

  /**
   * 解析Ai对话，获取历史会话，需要解析Ai回复
   */
  parseAssistantDialog(data) {
    this.data.dialogList.forEach(d => {
      if (d.id === data.did) {
        d.htmlContent = data.content;
        this.setData({
          dialogList: this.data.dialogList
        });
        this.scrollableBottom();
      }
    });
  },

  /**
   * 网络监听
   */
  networkHandle() {
    const that = this;

    const debouncedToast = debounce(
      (msg) => {
        if (that.data.isLoadingAi) {
          that.stopAiClick();
        }
        that.data.jxtToast.showToast(msg, "warning");
      },
      1000, false, true
    );

    // 弱网检查
    wx.onNetworkWeakChange(function (res) {
      if (res.weakNet && res.networkType !== "none") {
        debouncedToast("网络不稳定");
      }
      if (res.networkType === "none") {
        debouncedToast("网络异常");
      }
    });

    // 网络状态变化
    wx.onNetworkStatusChange(function (res) {
      if (!res.isConnected) {
        debouncedToast("网络异常");
      } else {
        debouncedToast("网络不稳定");
      }
    });

  },

  /**
   * 获取会话ID
   */
  getSid() {
    if (!hasJwt()) {
      return;
    }
    wx.fetch({
        url: "/ai/sid",
      })
      .then(res => {
        const resData = res.data;
        sessionID = resData.sid;
      });
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    this.$effect(() => {
      this.setData({
        isLogin: this.$getStoreData().isLogin,
      })
    });
    this.networkHandle();
    // 获取一些设备系统信息，协助生成顶部标题栏
    const windowInfo = wx.getWindowInfo();
    const menu = wx.getMenuButtonBoundingClientRect()
    if (windowInfo && menu) {
      let screenHeight = windowInfo.screenHeight;
      let statusBarHeight = windowInfo.statusBarHeight;
      let menuHeight = menu.height + (menu.top - statusBarHeight) * 2;
      let scrollHeight = screenHeight - statusBarHeight - menuHeight - 70;
      let scrollSessionHeight = `calc(100vh - ${ statusBarHeight + menuHeight + 94 }px)`;
      this.setData({
        systemStatusHeight: statusBarHeight,
        menuWidth: menu.width,
        navigationBarHeight: menuHeight,
        scrollHeight: scrollHeight,
        scrollSessionHeight: scrollSessionHeight
      });
    }
    this.getAiPageInfo();
    this.getSid();
  },

  /**
   * 拦截元素内部冒泡事件
   */
  noop() {
    // 空函数
  },

  /**
   * 获取Ai页面信息
   */
  getAiPageInfo() {
    wx.fetch({
        url: "/ai",
        toast: false
      })
      .then(res => {
        const resData = res.data;
        this.setData({
          info: resData
        })
      })
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {
    this.data.jxtToast = this.selectComponent("#jxtToast");
    // 初始化语音识别管理器
    this.initRecordRecognition();
    // 注册worker消息监听器
    _app.registerWorkerListener(this.workerMessageHandle);
    // 获取会话
    this.refreshSession();
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {
    // 注销worker监听器
    _app.unregisterWorkerListener(this.workerMessageHandle);
    // 移除网络监听器
    wx.offNetworkWeakChange();
    wx.offNetworkStatusChange();
  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage(e) {
    const that = this;
    if (e.from === "button") {
      if (that.data.isLoadingAi) {
        this.data.jxtToast.showToast("Ai正在回答喔~", "prompt");
        return;
      }
      const index = e.target.dataset.index
      if (index >= 1) {
        const promise = new Promise(resolve => {
          wx.fetch({
              url: "/ai/share",
              method: "POST",
              data: {
                question: that.data.dialogList[index - 1].content,
                answer: that.data.dialogList[index].content
              }
            })
            .then(res => {
              resolve({
                title: "分享我与Ai助手的对话",
                path: "/pages/ai/share/share?code=" + res.data.code,
                imageUrl: that.data.server + "/public/images/publicize01.png",
              });
            });
        })
        return {
          title: "西工商Ai辅助教学助手",
          path: "/pages/index/index",
          imageUrl: that.data.server + "/public/images/publicize01.png",
          promise
        }
      }
    }
    return {
      title: "西工商Ai辅助教学助手",
      path: "/pages/index/index",
      imageUrl: that.data.server + "/public/images/publicize01.png"
    }
  },
  //===============================================================

  /**
   * 切换会话
   */
  switchSession(e) {
    if (sessionID === e.currentTarget.dataset.session.sid) {
      this.closeSessionListClick();
      return;
    }
    dialogLastId = 922337203685477;
    sessionID = e.currentTarget.dataset.session.sid;
    this.setData({
      dialogList: [],
      hasNewDialogPage: true
    });
    this.getDialogs();
    this.closeSessionListClick();
  },

  /**
   * 点击添加新会话
   */
  addNewSessionClick() {
    if (this.data.dialogList.length === 0) {
      this.data.jxtToast.showToast("可直接提问喔~", "prompt");
    } else {
      // 获取新的会话ID
      this.getSid();
      this.setData({
        dialogList: []
      })
    }
    this.closeSessionListClick();
  },

  /**
   * 添加会话
   */
  addSession(id, title, create_time = Date.parse(new Date()) / 1000) {
    this.data.sessionList.push({
      id,
      title,
      create_time
    });
    this.setData({
      sessionList: this.data.sessionList
    });
  },

  /**
   * 关闭会话列表
   */
  closeSessionListClick() {
    this.setData({
      isShowSession: false
    });
  },

  /**
   * 前往登录页面
   */
  toLoginPage() {
    wx.redirectTo({
      url: '/pages/login/login'
    })
  },

  /**
   * 获取会话列表
   */
  getSessions() {
    if (!hasJwt()) {
      return;
    }
    if (!this.data.hasNewSessionPage || this.data.isLoadingSession) {
      // 无新页
      return;
    }
    let that = this;
    that.setData({
      isLoadingSession: true
    });
    wx.fetch({
        url: "/ai/session",
        data: {
          lastId: sessionLastId
        }
      })
      .then(res => {
        const resData = res.data;
        let hns = true;
        let sl = [...that.data.sessionList];
        if (resData.sessions.length > 0) {
          sl = [...that.data.sessionList, ...resData.sessions]
          sessionLastId = resData.sessions[resData.sessions.length - 1].id;
        }
        if (resData.sessions.length < 10) {
          hns = false;
        }
        that.setData({
          sessionList: sl,
          hasNewSessionPage: hns,
          isLoadingSession: false
        });
      })
      .catch(_ => {
        that.setData({
          isLoadingSession: false
        });
      });
  },

  /**
   * 刷新会话
   */
  refreshSession() {
    if (!hasJwt()) {
      // 未登录
      return;
    }
    this.setData({
      sessionList: [],
      hasNewSessionPage: true
    })
    sessionLastId = 922337203685477;
    this.getSessions();
  },
})